// Copyright Satoshi Nakajima (@snakajima)
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

(function(module_) {
  var module = module_ || require.register('comet.io');
  var exports = module.exports;
 
  var util = require('util');
  var URL = require('url');
  var events = require('events');
  var fs = require('fs');

  function Socket(uuid) {
    this._uuid = uuid;
    this._queue = [];
  }
  util.inherits(Socket, events.EventEmitter);
 
  // the app-specific property (optional), which will make it easy to debug
  Socket.prototype.name = '.';
 
  Socket.prototype._emit = events.EventEmitter.prototype.emit;
 
  Socket.prototype.emit = function(event, params) {
    var self = this;
    // Special case for events generated by EventEmitter
    if (event == 'newListener') {
      self._emit(event, params);
      return;
    }

    // We MUST clean up the refreshing timer here
    if (self._refresher) {
      console.log('comet: clear refresher');
      clearTimeout(self._refresher);
      delete self._refresher;
    }

    var payload = JSON.stringify( { event:event, params:params } );
    if (self._response) {
      //console.debug('comet: sending an event %s to %s (%s)', event, self.name
      //  /*, process._ios.objectID(self._response._socket)*/);
      self._response.writeHead(200, {
            // UTF-8 issue
            // 'Content-Length':payload.length,
            'content-type':'application/json',
            'Cache-Control': 'no-cache' } );
      self._response.end(payload);
      delete self._response;
    } else {
      console.log('comet: queuing an event %s for %s', event, self.name);
      self._queue.push(payload);
    }
  };
 
  function Server() {
    this._sockets = {}; // uuid:socket pairs
  }
  util.inherits(Server, events.EventEmitter);
 
  // Generate locally unique ID during the lifetime of this application
  var unique_id = (function() {
    var seed = 0;
    return function() {
      var id = (new Date()).getTime() + '-' + (seed++).toString();
      seed %= 10000;
      return id;
    };
  })();

  Server.prototype.serve = function(req, res) {
    var self = this;
    var ret = false;
    var url = URL.parse(req.url, true);
    var result = RegExp('^\/_comet\.io\/([a-zA-Z0-9\s\._-]+)$').exec(url.pathname);
    if (result) {
      var cmd = result[1];
      //console.log('push.io: ' + cmd);
      if (cmd == '_connect') {
        var uuid = unique_id();;
        var socket = new Socket(uuid);
        self._sockets[uuid] = socket;
        console.log('comet: ++sockets = ' + Object.keys(self._sockets).length);
        var payload = JSON.stringify( { uuid:uuid } );
        res.writeHead(200, { 'Content-Length':payload.length } );
        res.end(payload);
        self.emit('connection', socket);
      } else if (cmd == '_wait') {
        var socket = self._sockets[url.query.uuid];
        if (socket) {
          if (socket._queue.length > 0) {
            // We already have something pending in the queue. Send it now. (LATER: them?)
            console.log('comet dequeueing for %s', socket.name);
            var payload = socket._queue.shift();
            // UTF-8 issue
            res.writeHead(200, { /* 'Content-Length':payload.length  */ });
            res.end(payload);
          } else {
            // Queue is empty. Let the client wait untile we have something to send.
            socket._response = res;
            console.log('comet: waiting... (%s)', socket.name);
 
            // Refresher sends an empty commet message after N secs to keep it fresh.
            var refresher = function() {
              if (socket._response) {
                var payload = JSON.stringify({});
                socket._response.writeHead(200, { 'Content-Length':payload.length } );
                socket._response.end(payload);
                console.log('comet: sending an empty message to keep it fresh (%s, %d)', socket.name, payload.length);
              }
              delete socket._refresher;
            };
            socket._refresher = setTimeout(refresher, 30000);
 
            // Handle special case
            res.on('close', function() {
              console.log('coment client terminate the connection first (%s)', socket.name
                  /*,
                  process._ios.objectID(socket._response._socket), process._ios.objectID(res._socket)*/);
              // Client terminate the connection first
              delete socket._response;
              
              // This socket became a zombi, terminate it unless it reconnects within 1 sec
              socket._zombi = true;
              setTimeout(function() {
                if (socket._zombi) {
                  delete self._sockets[socket._uuid];
                  console.log('comet: terminated a zombi (%s) --count = %d',
                              socket.name, Object.keys(self._sockets).length);
                  socket._emit('disconnect');
                }
              }, 1000);
            });
          }
        } else {
          console.error('comet/_wait: no socket for %s', url.query.uuid);
        }
      } else if (cmd == 'comet.io.client.js') {
        fs.readFile(__dirname + '/comet.io.client.js', 'utf8', function(err, data) {
          if (err) {
            console.log(err);
          }
          res.writeHead(200, { 'Content-Type':'text/javascript' } );
          res.end(data);
        });
      } else {
        var socket = self._sockets[url.query._uuid];
        var code = 200;
        if (socket) {
          socket._emit(cmd, url.query);
        } else {
          console.error('comet/%s: no socket for %s', cmd, url.query.uuid);
          code = 404; // not found
        }
        var payload = JSON.stringify({});
        res.writeHead(code, { 'Content-Length':payload.length } ); // REVIEW: not unicode safe!
        res.end(payload);
      }
      ret = true;
    }
    return ret;
  };
 
  exports.createServer = function() {
    return new Server();
  };
})(typeof module != 'undefined' ? module : null);
